package com.example.shiro;

import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.example.business.entity.*;
import com.example.business.service.*;
import org.apache.shiro.authc.*;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.util.ByteSource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;

import javax.annotation.Resource;
import java.io.UnsupportedEncodingException;
import java.util.List;

/**
 * 自定义的Realm，用于身份认证和权限获取,自定义权限匹配
 */
public class MyShiroRealm extends AuthorizingRealm {

    private static final Logger logger = LoggerFactory.getLogger(MyShiroRealm.class);


    @Resource
    private SysRoleService sysRoleService;

    @Resource
    private SysPermissionService sysPermissionService;

    @Autowired
    private SysUserService sysUserService;

    @Autowired
    private SysRoleUserService sysRoleUserService;

    @Autowired
    private SysRolePermissionService sysRolePermissionService;


    /**
     * 认证信息.(身份验证) : Authentication 是用来验证用户身份
     * 提供账户信息返回认证信息（用户的角色信息集合）
     * DisabledAccountException （禁用的帐号）
     * LockedAccountException （锁定的帐号）
     * UnknownAccountException（错误的帐号）
     * ExcessiveAttemptsException（登录失败次数过多）
     * IncorrectCredentialsException （错误的凭证）
     * ExpiredCredentialsException （过期的凭证
     *
     * @param token
     * @return
     * @throws AuthenticationException
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        System.out.println("---------------- 执行 Shiro 凭证认证 ----------------------");
        //获取用户的输入的账号.
        String userName = (String) token.getPrincipal();
        SysUser sysUser = sysUserService.getOne(new QueryWrapper<SysUser>().eq("name", userName));
        if (null == sysUser) {
            throw new UnknownAccountException();
        }
        //账户冻结
        if ("1".equals(sysUser.getState())) {
            throw new LockedAccountException("帐号[" + userName + "]已被禁止登录！请联系管理员");
        }
        // 第 1 个参数可以传一个实体对象，然后在认证的环节可以取出
        // 第 2 个参数应该传递在数据库中“正确”的数据，然后和 token 中的数据进行匹配
        if (null != sysUser) {
            SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(sysUser, sysUser.getPassword(), getName());
            // 设置盐值
            ByteSource credentialsSalt = null;
            try {
                credentialsSalt = ByteSource.Util.bytes(userName.getBytes("UTF-8"));
            } catch (UnsupportedEncodingException e) {
                e.printStackTrace();
            }
            info.setCredentialsSalt(credentialsSalt);
            System.out.println("---------------- Shiro 凭证认证成功 ----------------------");
            return info;
        }
        throw new UnknownAccountException();
    }


    /**
     * 授权用户权限
     * 授权的方法是在碰到<shiro:hasPermission name=''></shiro:hasPermission>标签的时候调用的
     * 它会去检测shiro框架中的权限(这里的permissions)是否包含有该标签的name值,如果有,里面的内容显示
     * 如果没有,里面的内容不予显示(这就完成了对于权限的认证.)
     * <p>
     * shiro的权限授权是通过继承AuthorizingRealm抽象类，重载doGetAuthorizationInfo();
     * 当访问到页面的时候，链接配置了相应的权限或者shiro标签才会执行此方法否则不会执行
     * 所以如果只是简单的身份认证没有权限的控制的话，那么这个方法可以不进行实现，直接返回null即可。
     * <p>
     * 在这个方法中主要是使用类：SimpleAuthorizationInfo 进行角色的添加和权限的添加。
     * authorizationInfo.addRole(role.getRole()); authorizationInfo.addStringPermission(p.getPermission());
     * <p>
     * 当然也可以添加set集合：roles是从数据库查询的当前用户的角色，stringPermissions是从数据库查询的当前用户对应的权限
     * authorizationInfo.setRoles(roles); authorizationInfo.setStringPermissions(stringPermissions);
     * <p>
     * 就是说如果在shiro配置文件中添加了filterChainDefinitionMap.put("/add", "perms[权限添加]");
     * 就说明访问/add这个链接必须要有“权限添加”这个权限才可以访问
     * <p>
     * 如果在shiro配置文件中添加了filterChainDefinitionMap.put("/add", "roles[100002]，perms[权限添加]");
     * 就说明访问/add这个链接必须要有 "权限添加" 这个权限和具有 "100002" 这个角色才可以访问
     * <p>
     * 授权 权限认证，为当前登录的Subject授予角色和权限（角色的权限信息集合）
     *
     * @param principals
     * @return
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        System.out.println("权限配置-->MyShiroRealm.doGetAuthorizationInfo()");
        System.out.println("---------------- 执行 Shiro 权限获取 ---------------------");
        SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
        try {
            SysUser user = (SysUser) principals.getPrimaryPrincipal();
            String userId = user.getId();
            //查询当前用户的角色（沒有角色的控制可以不用）
            List<SysRoleUser> sysRoleUserList = sysRoleUserService.list(new QueryWrapper<SysRoleUser>().eq("user_id", userId));
            for (SysRoleUser sysRoleUser : sysRoleUserList) {
                SysRole sysRole = sysRoleService.getOne(new QueryWrapper<SysRole>().eq("id", sysRoleUser.getRoleId()));
                if (null != sysRole) {
                    authorizationInfo.addRole(sysRole.getId());
                    List<SysRolePermission> sysRolePermissionList = sysRolePermissionService.list(new QueryWrapper<SysRolePermission>().eq("role_id", sysRole.getId()));
                    for (SysRolePermission sysRolePermission : sysRolePermissionList) {
                        authorizationInfo.addStringPermission(sysRolePermission.getPermissionId());
                    }
                } else {
                    throw new IllegalArgumentException();
                }
            }
            logger.debug("用户权限:" + JSON.toJSONString(authorizationInfo));
            System.out.println("---- 获取到以下权限 ----");
            System.out.println("用户权限:" + JSON.toJSONString(authorizationInfo));
            System.out.println("---------------- Shiro 权限获取成功 ----------------------");
        } catch (Exception e) {
            e.printStackTrace();
            logger.error(e.toString());
        }
        return authorizationInfo;
    }

}
